package core.arvore;

import java.util.ArrayList;
import java.util.List;

import core.jogo.Tabuleiro;
import core.redeNeural.RedeNeural;
import core.util.Temporizador;

public class Node {

	//Se n�o � max, � mini. 
	private boolean isMax; 

	private int valor;
	private Tabuleiro tabuleiro;
	private RedeNeural rede;
	private Tabuleiro melhorJogada;
	private Node melhorFilho;

	public RedeNeural getRede() {
		return rede;
	}

	public void setRede(RedeNeural rede) {
		this.rede = rede;
	}

	private int profundidadeMax = 4;//Por enquanto, sempre par


	private Node pai;
	private List<Node> filhos = new ArrayList<Node>();

	//	Construtores

	public Node(){
		this.isMax = false;
		this.valor = 0;
	}

	public Node(int valor){
		this.isMax = false;
		this.valor = valor;
	}

	public Node(boolean isMax, RedeNeural rede, Tabuleiro t, int profundidadeMax){
		this.isMax = isMax;
		this.rede = rede;
		this.tabuleiro = t;
		this.profundidadeMax = profundidadeMax;
	}

	public double getValue(double alfaPai){
		Temporizador temp = Temporizador.getInstancia();
		if(getProfundidade() == getProfundidadeMax()){
			int i=0;
			double saida = 0, melhorJogada = 0;
			//			System.out.println("Profundidade fim:" + this.getProfundidade());
			for(Tabuleiro t : getTabuleiro().jogadasPossiveis()){
				if(temp.isTempoCritico()){
					//					System.out.println("Abrindo folha");
					break;
				}
				saida = getRede().avaliaTabuleiro(t.getCasas());
				//				System.out.println(saida);
				if(saida > melhorJogada){
					melhorJogada = saida;
					if(isRaiz()){
						setMelhorJogada(t);
					}
				}				
				i++;	
			}
			//			System.out.println("Jogada escolhida Fim: " + melhorJogada);
			return melhorJogada; //Se não há jogada, retorna 0
		}
		else{						
			if(isMax){
				//				System.out.println("Profundidade Max:" + this.getProfundidade());
				if(getFilhos().size() == 0)
					for(Tabuleiro t : getTabuleiro().jogadasPossiveis()){
						Node n = new Node(!isMax, getRede(), t, getProfundidadeMax());
						adicionarFilho(n);
					}
				double alfa = -1;
				Tabuleiro melhorJogada = null;
				if(getFilhos().size() > 0) //Evita que max não faça uma jogada só pq é ruim, mesmo sendo obrigatória
					setMelhorFilho(getFilhos().get(0));
				double value;
				//				long inicial = System.currentTimeMillis();
				for(Node f : getFilhos()){					
					if(temp.isTempoCritico()){
						//						System.out.println("Abrindo prof = " + getProfundidade());
						break;
					}
					value = f.getValue(alfa);
					if(getPai() == null){
						//						System.out.println("Abriu galho da raiz " + (System.currentTimeMillis() - inicial));
					}
					//					System.out.println(value);
					if(value > alfa){
						alfa = value;
						if(isRaiz()){
							setMelhorFilho(f);
						}
					}
				}
				if(isRaiz()){
					if(getMelhorFilho() != null)
						setMelhorJogada(getMelhorFilho().getTabuleiro());
				}
				//				System.out.println("Jogada escolhida Max: " + alfa);
				return alfa;
			}
			else{
				//				System.out.println("Profundidade Min:" + this.getProfundidade());
				//Se for uma jogada do min, é preciso inverter o tabuleiro antes de avaliar as jogadas possíveis
				//E depois desinvertê-lo
				Tabuleiro auxTab = getTabuleiro().inverter();
				if(getFilhos().size() == 0)
				for(Tabuleiro t : auxTab.jogadasPossiveis()){
					Node n = new Node(!isMax, getRede(), t.inverter(), getProfundidadeMax());
					adicionarFilho(n);
				}
				double min = 2,value;				
				for(Node f : getFilhos()){
					if(temp.isTempoCritico()){
						//						System.out.println("Abrindo prof = " + getProfundidade());
						break;
					}
					value = f.getValue(0); //Aqui não importa, não se passa o valor de alfa
					//					System.out.println(value);
					if(value < min){
						min = value;
						if(min < alfaPai){
							break;
						}
					}
				}
				//				System.out.println("Jogada escolhida Min: " + min);
				return min;
			}
		}
	}

	//	M�todos Gen�ricos
	public boolean isRaiz(){
		if( this.getPai() != null){
			return false;
		}
		return true;
	}

	public boolean isFolha(){
		if( this.getFilhos().isEmpty() ){
			return true;
		}
		return false;
	}

	public int getProfundidade(){
		if(this.getPai() == null ) return 0;
		int profundidade = 0;

		for( Node n = this ; !n.isRaiz() ;  n = n.getPai() ){

			profundidade ++;
		}
		return profundidade;
	}

	public int getLarguraDosFilhos(){
		return this.getFilhos().size();
	}


	public String nodeToString(){
		String result = "";
		if(this.isRaiz())
			result += 
				this.getIndice()
				+"|"+
				this.getValor()
				+"| |" + 
				this.getProfundidade();

		else
			result += 
				this.getIndice()
				+"|"+
				this.getValor()
				+"|"+ 
				this.getPai().getIndice()
				+ "|" +
				this.getProfundidade();


		return result;
	}


	public void adicionarFilho(Node n){
		n.setPai(this);
		//		n.setIndice(this.getFilhos().size()) ;
		this.getFilhos().add(n);
	}

	//	Getters E Setters
	public boolean isMax() {
		return isMax;
	}
	public void setMax(boolean isMax) {
		this.isMax = isMax;
	}

	public int getValor() {
		return valor;
	}
	public void setValor(int valor) {
		this.valor = valor;
	}

	public Node getPai() {
		return pai;
	}
	public void setPai(Node pai) {
		this.pai = pai;
	}

	public List<Node> getFilhos() {
		return filhos;
	}
	public void setFilhos(List<Node> filhos) {
		for(int i = 0 ; i < filhos.size() ; i ++){
			filhos.get(i).setPai(this);
		}
		this.filhos = filhos;
	}

	public int getIndice() {
		if(this.isRaiz()) return -1;
		else
			return this.getPai().getFilhos().indexOf(this);

	}

	public Tabuleiro getTabuleiro() {
		return tabuleiro;
	}

	public void setTabuleiro(Tabuleiro tabuleiro) {
		this.tabuleiro = tabuleiro;
	}

	public Tabuleiro getMelhorJogada() {
		return melhorJogada;
	}

	public void setMelhorJogada(Tabuleiro melhorJogada) {
		this.melhorJogada = melhorJogada;
	}

	public int getProfundidadeMax() {
		return profundidadeMax;
	}

	public void setProfundidadeMax(int profundidadeMax) {
		this.profundidadeMax = profundidadeMax;
	}

	public Node getMelhorFilho() {
		return melhorFilho;
	}

	public void setMelhorFilho(Node melhorFilho) {
		this.melhorFilho = melhorFilho;
	}



}
